package net.lwf.jaxb.util.maths.fraction;

import java.util.List;
import java.util.StringTokenizer;

import net.lwf.common.constant.ICommonConstant;
import net.lwf.jaxb.entity.Fraction;

public class FractionUtil {
	
	//http://www.speakeasy.org/~shilad/jazz/src/util/Fraction.html
	public static Fraction add(List<Fraction> fractions) {
		Fraction one = fractions.get(0);
		Fraction two = fractions.get(1);
		Fraction result = add(one, two);
		if(fractions.size() > 2) {
			for(int i=2; i<fractions.size(); i++) {
				Fraction fraction = (Fraction) fractions.get(i);
				result = add(result, fraction);
			}
		}
		return result;
	}
	
	public static Fraction add(Fraction one, Fraction two){
		int num1 = one.getNumerator();
		int num2 = two.getNumerator();
		int denom1 = one.getDenominator();
		int denom2 = two.getDenominator();
		
		// Method 2
		int numer, denom;

		numer = num1 * denom2 + num2 * denom1;
		denom = denom1 * denom2;
		return reduce(new Fraction(numer, denom));
		
/*		int newNum = (num1 * denom2) + (num2 * denom1);
		int newDenom = (denom1 * denom2);	
			
		if (newNum == 0)
			return new Fraction(0, newDenom);
		
		if (newNum < 0 || newDenom < 0){
			// if result is negative, convert to positive then calculate GCF and reduce
			newNum = Math.abs(newNum);
			newDenom = Math.abs(newDenom);
			int gcf = calcGCF(newNum, newDenom);
			newNum = newNum / gcf;
			newDenom = newDenom / gcf;
			return new Fraction(-newNum, newDenom);   // convert back to negative
		}
		else{
			// reduces fraction
			int gcf = calcGCF(newNum, newDenom);
			newNum = newNum / gcf;
			newDenom = newDenom / gcf;
			return new Fraction(newNum, newDenom);
		}*/
	}// end addition

	// Subtraction
											
	public static Fraction subtract(Fraction one, Fraction two){
		int num1 = one.getNumerator();
		int num2 = two.getNumerator(); //--> x
		int denom1 = one.getDenominator();
		int denom2 = two.getDenominator(); // --> x
		
		// Method 2
		int numer, denom;

		numer = num1 * denom2 - num2 * denom1;
		denom = denom1 * denom2;
		return reduce(new Fraction(numer, denom));
		
//		// if subtracting a negative, make it add
//		if (num2 < 0 || denom2 < 0){
//			int newNum = (num1 * denom2) + (Math.abs(num2) * denom1);
//		    int newDenom = (denom1 * Math.abs(denom2));
//		    if (newNum == 0){
//		    	return new Fraction(0, newDenom);
//		    }
//		    if (newNum < 0 || newDenom < 0){
//		    	newNum = Math.abs(newNum);
//			    newDenom = Math.abs(newDenom);
//			    int gcf = calcGCF(newNum, newDenom);
//			    newNum = newNum / gcf;
//			    newDenom = newDenom / gcf;
//			    return new Fraction(-newNum, newDenom);
//		    }else{
//		    	int gcf = calcGCF(newNum, newDenom);
//			    newNum = newNum / gcf;
//			    newDenom = newDenom / gcf;
//			    return new Fraction(newNum, newDenom);
//		    }
//		}
//		else{	
//			int newNum = (num1 * denom2) - (num2 * denom1);
//			int newDenom = (denom1 * denom2);
//			
//			if (newNum == 0){
//				return new Fraction(0, 0);
//			}
//			if (newNum < 0 || newDenom < 0){
//				newNum = Math.abs(newNum);
//				newDenom = Math.abs(newDenom);
//				int gcf = calcGCF(newNum, newDenom);
//				newNum = newNum / gcf;
//				newDenom = newDenom / gcf;
//				return new Fraction(-newNum, newDenom);
//			}
//			else{
//				int gcf = calcGCF(newNum, newDenom);
//				newNum = newNum / gcf;
//				newDenom = newDenom / gcf;
//				return new Fraction(newNum, newDenom);
//			}
//		}
	}// end subtraction
	
	// Multiplication
	public static Fraction multiply(Fraction one, Fraction two){
		int num1 = one.getNumerator();
		int num2 = two.getNumerator();
		int denom1 = one.getDenominator();
		int denom2 = two.getDenominator();
		
		int newNum = (num1 * num2);
		int newDenom = (denom1 * denom2);
		
		if (newNum == 0){
			return new Fraction(0, newDenom);
		}
		if (newNum < 0 || newDenom < 0){
			newNum = Math.abs(newNum);
			newDenom = Math.abs(newDenom);
			int gcf = calcGCF(newNum, newDenom);
			newNum = newNum / gcf;
			newDenom = newDenom / gcf;
			return new Fraction(-newNum, newDenom);
		}else{
			int gcf = calcGCF(newNum, newDenom);
			newNum = newNum / gcf;
			newDenom = newDenom / gcf;
			return new Fraction(newNum, newDenom);
		}
	}// end multiplication
	
	// Division
	public static Fraction divide(Fraction one, Fraction two){
		int num1 = one.getNumerator();
		int num2 = two.getNumerator();
		int denom1 = one.getDenominator();
		int denom2 = two.getDenominator();
		
		int newNum = (num1 * denom2);
		int newDenom = (denom1 * num2);
		
		if (newNum == 0){
			return new Fraction(0, newDenom);
		}
		if (newNum < 0 || newDenom < 0){
			newNum = Math.abs(newNum);
			newDenom = Math.abs(newDenom);
			int gcf = calcGCF(newNum, newDenom);
			newNum = newNum / gcf;
			newDenom = newDenom / gcf;
			return new Fraction(-newNum, newDenom);
		}else{
			int gcf = calcGCF(newNum, newDenom);
			newNum = newNum / gcf;
			newDenom = newDenom / gcf;
			return new Fraction(newNum, newDenom);
		}
	}// end division
	
	/**
	 * Compares Fraction one with Fraction two. Returns the value
	 * by comparing the Fraction one status.
	 * 
	 * @param one
	 * @param two
	 * @return
	 */
	public static String compare(Fraction one, Fraction two) {
		String status = ICommonConstant.EQUAL_TO;
		if(one.getDecimalFraction() > two.getDecimalFraction()) {
			status = ICommonConstant.GREATER_THAN;
		}
		else {
			status = ICommonConstant.LESS_THAN;
		}
		return status;
	}
	
	public static Fraction getMaxFraction(Fraction one, Fraction two) {
		if(one.getDecimalFraction() > two.getDecimalFraction()) {
			return one;
		}
		else if(one.getDecimalFraction() < two.getDecimalFraction()) {
			return two;
		}
		else {
			//throw exception stating equal fractions
		}
		return null;
	}
	
	public static Fraction getMinFraction(Fraction one, Fraction two) {
		if(one.getDecimalFraction() > two.getDecimalFraction()) {
			return two;
		}
		else if(one.getDecimalFraction() < two.getDecimalFraction()) {
			return one;
		}
		else {
			//throw exception stating equal fractions
		}
		return null;
	}
	
	public static Fraction toFraction(double value) {
		if(value != 0) {
			String sValue = String.valueOf(value);
			int decimalposition = sValue.indexOf(".");
			int digits_after_decimal = sValue.substring(decimalposition).length();
			int denominator = 1;
			for(int i=0; i<digits_after_decimal; i++) {
				denominator *= 10;
			}
			int numerator = (int)(value * denominator);
			return reduce(new Fraction(numerator, denominator));
		}
		return null;
	}

	
	// Find greatest common factor
	public static int calcGCF(int a, int b){
		int s;
		if (a > b){
			s = b;
		}else{
			s = a;
		}
		for (int i = s; i > 0; i--){
			if ((a%i == 0) && (b%i == 0)){
				return i;
			}
		}
		return -1;
	}
	
	public static int gcd(int a, int b) {
		while (b > 0) {
			int temp = b;
			b = a % b; // % is remainder
			a = temp;
		}
		return a;
	}

	//greatest common divisor
	public static long gcd(int[] input) {
		int result = input[0];
		for (int i = 1; i < input.length; i++)
			result = gcd(result, input[i]);
		return result;
	}
	
	public static int lcm(int a, int b) {
		return a * (b / gcd(a, b));
	}

	//Least common multiple
	public static int lcm(int[] input) {
		int result = input[0];
		for (int i = 1; i < input.length; i++)
			result = lcm(result, input[i]);
		return result;
	}

	public static int gcf(int a, int b) {
		if (b == 0)
			return a;
		else
			return (gcf(b, a % b));
	}

	
/*    public static Fraction reduce(Fraction fraction) {
    	int num = fraction.getNumerator();
    	int den = fraction.getDenominator();
		int gcd = calcGCF(fraction.getNumerator(), fraction.getDenominator());
		int x;

		if (num < den) {
			x = num;
		} else {
			x = den;
		}

		while (!((num % gcd) == 0) && ((den % gcd) == 0)) {
			x--;
			gcd = x;
		}
		num = num / gcd;
		den = den / gcd;
		return new Fraction(num, den);
	}*/
	
	public static Fraction reduce(Fraction fraction) {
		int numer = fraction.getNumerator();
		int denom = fraction.getDenominator();

		if(fraction.isNagative()) {
			if(numer < 0) {
				numer = -1 * numer;
			}
			if(denom < 0) {
				denom = -1 * denom;
			}
		}
		int	d;

		while (true) {
			d = gcd(numer, denom);
			if (d == 1) {
				return fraction;
			}
			else {
				numer /= d;
				denom /= d;
				if(fraction.isNagative()) {
					if(fraction.getNumerator() < 0) {
						numer = -1 * numer;
					}
					if(fraction.getDenominator() < 0) {
						denom = -1 * denom;
					}
				}
				return new Fraction(numer, denom);
			}

		}
	}
    
    /**
     * Converts mixed fraction (from String) to Fraction
     * Ex. 1 3/4
     * 
     * @param mixed
     * @param reduce
     * @return
     */
    public static Fraction mixedToFraction(String mixed, boolean reduce) {
    	Fraction fraction = null;
    	if(mixed != null && mixed.trim().length() != 0) {
    		StringTokenizer stk = new StringTokenizer(mixed, ICommonConstant.ONE_SPACE);
    		int whole = Integer.parseInt(stk.nextToken());
    		String s_fraction = stk.nextToken();
    		if(s_fraction != null && s_fraction.trim().length() != 0) {
    			StringTokenizer frstk = new StringTokenizer(s_fraction, ICommonConstant.FORWARD_SLASH);
    			int numerator = Integer.parseInt(frstk.nextToken());
    			int denomenator = Integer.parseInt(frstk.nextToken());
    			fraction = new Fraction(((whole * denomenator) + numerator), denomenator);
    		}
    		else {
    			fraction = new Fraction(whole, whole);
    		}
    	}
    	if(reduce) {
    		return reduce(fraction);
    	}
    	return fraction;
    }
    
    public static Fraction toFraction(String s_fraction, boolean reduce) {
    	Fraction fraction = null;
		if(s_fraction != null && s_fraction.trim().length() != 0) {
			StringTokenizer frstk = new StringTokenizer(s_fraction, ICommonConstant.FORWARD_SLASH);
			if(frstk.countTokens() == 1) {
				fraction = new Fraction(1, 1);
			}
			else {
				fraction = new Fraction(Integer.parseInt(frstk.nextToken()), Integer.parseInt(frstk.nextToken()));
			}
		}
    	if(reduce) {
    		return reduce(fraction);
    	}
    	return fraction;
    }

}
